using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;

public class NetworkEntityMgr : MonoBehaviour {
	
	public PlayerGUI playerGUI;
	
	// Containers set from BuildScreen::Start().
	private Transform buildScreenContainer,
					  weaponContainer,
					  inventoryContainer;
	
	private ParticleSystem emitter;
	
	private MiniMap minimap;

	// Sends the specified entity to the World state.
	public void toWorld(Entity entity, Vector3 position) {
		Listeners.inst().notify(Channel.ON_ENTITY_DROPPED, entity.getOwner().GetComponent<PlayerState>());
		stateTransformation(entity, null, LayerMgr.LAYER_WORLD, position);
	}
	
	// Sends the specified entity to the inventory from the World state.
	public void fromWorld(Entity entity) {
		stateTransformation(entity, inventoryContainer, LayerMgr.LAYER_INVENTORY);
	}
	
	// Sends the specified entity to the Weapon state.
	public void toWeapon(Entity entity) {
		HandlePoint p = entity.getHandlePoint();
		
		Vector3 pos 	= Vector3.zero;
		Quaternion rot 	= Quaternion.identity;
		
		// If a handle exists.
		if (p != null) {
			pos = -p.transform.localPosition;
			rot = Quaternion.LookRotation(p.transform.forward);
			
			Debug.Log(
				"Adding weapon with handle point. Pos: " + 
				pos + 
				", Rot: " + 
				rot
			);
		} else {
			Debug.LogWarning ("The root entity of the currently equiped weapon does not have a handle point. Please add one to the prefab.");
		}
		
		// Transform space.
		stateTransformation(entity, weaponContainer, LayerMgr.LAYER_WEAPON, pos, rot);
		
		Listeners.inst().notify(Channel.ON_WEAPON_SWAP, entity.getOwner().GetComponent<PlayerState>());
	}
	
	// Sends the specified entity to the inventory from the Weapon state.
	public void fromWeapon(Entity entity) {
		stateTransformation(entity, inventoryContainer, LayerMgr.LAYER_INVENTORY);
	}
	
	// Sends the specified entity to the Build Screen state.
	public void toBuildScreen(Entity entity, Vector3 position) {
		stateTransformation(entity, buildScreenContainer, LayerMgr.LAYER_BUILD, position);
	}
	
	// Sends the specified entity to the inventory from the Build Screen state.
	public void fromBuildScreen(Entity entity) {
		stateTransformation(entity, inventoryContainer, LayerMgr.LAYER_INVENTORY);
	}
	
	// Translates the Entity from one state to another synchronously.
	private void stateTransformation(
		Entity entity, 
		Transform parent, 
		int layer, 
		Vector3 position 	= default(Vector3), 
		Quaternion rotation = default(Quaternion)) {

		entity.transform.parent = parent;
		entity.setLocalPoint(null);
		entity.setTargetPoint(null);
		entity.setLayer(layer);
		entity.transform.localPosition 	= position;
		entity.transform.localRotation 	= rotation;
		
		if (parent != null && entity.transform.parent == null) {
			Debug.LogError("The Player Setup is wrong. All containers must be Transforms and not GameObjects.");
		}

		synchronizeEntity(entity);
		
		if (minimap != null) {
			minimap.updateMinimap();
		}
	}
	
	// Sends an RPC-call with an update of the specified state.
	private void synchronizeEntity(Entity entity) {
		
		// Determine the owner.
		updateOwner(entity);
		
		// Go through all subentities.
		entity.each(delegate(GameObject obj) {

			Entity e = obj.GetComponent<Entity>();
			
			if (e != null) {
				// The IDs used.
				int entityID		= e.entityID,
					parentID 		= -1,
					localPointID 	= 0,
					targetPointID 	= 0;
				
				// The parent node of the entity.
				Transform parent  		   = e.transform.parent;
				NetworkViewID parentViewID = NetworkViewID.unassigned;

				// If a parent exists...
				if (parent != null) {
					
					// Set parent ID
					if (parent.GetComponent<Entity>() != null) {
						parentID = parent.GetComponent<Entity>().entityID;
					}
					
					// Set local point ID
					if (e.getLocalPoint() != null) {
						localPointID = e.getLocalPoint().lockID;
					}
					
					// Set target point ID
					if (e.getTargetPoint() != null) {
						targetPointID = e.getTargetPoint().lockID;
					}
					
					// Set the network view
					if (parent.networkView != null) {
						parentViewID = parent.networkView.viewID;
					}
					
				}

				// Make sure the local Entity is correct.
				e.updateLock();
				
				// Set owner
				e.setOwner(entity.getOwner());

				// Call RPC.
				networkView.RPC(
					"rpcUpdateLock", 
					RPCMode.Others, 
					entityID,
					parentID,
					localPointID,
					targetPointID,
					obj.layer,
					e.transform.localPosition,
					e.transform.rotation,
					parentViewID
				);

			}
			
			return false;
		});
		
		// Update the layer of the Entity recursively.
		networkView.RPC(
			"rpcUpdateLayers", 
			RPCMode.Others, 
			entity.entityID, 
			entity.gameObject.layer
		);
	}
	
	// Recursively sets the layer of the specified entity.
	[RPC]
	public void rpcUpdateLayers(int entityID, int layer) {
		Entity entity = findEntityByID(entityID);
		entity.setLayer(layer);
		
		switch (layer) {
			case LayerMgr.LAYER_WEAPON: Listeners.inst().notify(Channel.ON_WEAPON_SWAP, entity.getOwner().GetComponent<PlayerState>()); 	break;
			case LayerMgr.LAYER_WORLD: 	Listeners.inst().notify(Channel.ON_ENTITY_DROPPED, entity.getOwner().GetComponent<PlayerState>()); break;
		}
		//HACK:
		//minimap.updateMinimap();
		updateOwner(entity);
	}
	
	// Updated the lock status of the specified entity on all clients.
	[RPC]
	public void rpcUpdateLock(
		int entityID,
		int parentID,
		int localPointID,
		int targetPointID,
		int layer,
		Vector3 position,
		Quaternion rotation,
		NetworkViewID parentViewID) {
		
		Entity entity = findEntityByID(entityID);
		
		// Make sure the mentioned entity exists.
		if (entity != null) {

			Entity parent = findEntityByID(parentID);
			
			// Make sure the parent exists.
			if (parent != null) {
				
				// Update lock.
				entity.transform.parent = parent.transform;
				
				LockPoint localPoint  = findLockPointByID(entity, localPointID), 
						  targetPoint = findLockPointByID(parent, targetPointID);
				
				entity.setLocalPoint(localPoint);
				entity.setTargetPoint(targetPoint);
				
			// If the parent does not exist.
			} else {
				// Find out if the parent is in fact a GameObject and not an Entity.
				if (parentViewID != NetworkViewID.unassigned) {
					Transform[] elements = Object.FindObjectsOfType(typeof(Transform)) as Transform[];
					
					foreach (Transform found in elements) {
						if (found != null
						&&  found.GetComponent<NetworkView>() != null
						&&  found.GetComponent<NetworkView>().viewID == parentViewID) {
							// A valid GameObject-parent was found.
							entity.transform.parent = found;
							break;
						}
					}
				}
			}
			
			// Update the transformation of the Entity.
			entity.transform.localPosition = position;
			entity.transform.rotation 	   = rotation;
			entity.gameObject.layer 	   = layer;
			updateOwner(entity);

		} else {
			Debug.LogError ("The entity mentioned in RPC (" + entityID + ") does not exist in local scene.");
		}
	}

	[RPC]
	public void spawnEntity(int id, int type, Vector3 pos, Quaternion rot) {
		PrefabHandler p = Object.FindObjectOfType(typeof(PrefabHandler)) as PrefabHandler;
		GameObject g 	= Instantiate(p.getPrefabByType(type), pos, rot) as GameObject;
		Entity entity 	= g.GetComponent<Entity>();
		entity.entityID = id;
		
		Debug.Log("SPAWNING!");
		minimap.updateMinimap();
		
		// Notify listeners.
		Listeners.inst().notify(Channel.ON_ENTITY_SPAWNED, null);
	}
	
	[RPC]
	public void destroyEntity(int id) {
		Entity entity = findEntityByID(id);

		// Notify listeners.
		Listeners.inst().notify(Channel.ON_ENTITY_DESTROYED, null);
		
		Destroy(entity.gameObject);
		minimap.updateMinimap();
	}
	
	//Particle Test
	[RPC]
	public void shootParticleRPC(Vector3 position, Vector3 velocity,float size){
		//Debug.Log("Someone is Shooting, shoot back!" + position + velocity + size);
		emitter.Emit(position, velocity, size, 1.0f, Color.yellow);
	}
	
	[RPC]
	public void rpcMelee(int entityID) {
		Entity entity = findEntityByID(entityID);
		Debug.Log("MeleeDamage");
		Listeners.inst().notify(Channel.ON_MELEE, entity.getOwner().GetComponent<PlayerState>());
	}
	
	[RPC]
	public void rpcRanged(int entityID) {
		Entity entity = findEntityByID(entityID);
		Listeners.inst().notify(Channel.ON_FIRE, entity.getOwner().GetComponent<PlayerState>());
	}
	
	[RPC]
	public void rpcBashArea(Vector3 position, float time, float distance) {
		BashListener.inst().notify(new BashEvent(position, time, distance));
	}
	
//	[RPC]
//	public void rpcUpdateScore(NetworkViewID player, int score) {
//		Debug.Log("Updating Score");
//		ScoreListener.inst().notify(new ScoreEvent(player, score));
//	}
	
	// Returns the LockPoint with the specified ID in the given Entity. If it does not exists,
	// null is returned.
	private LockPoint findLockPointByID(Entity e, int id) {
		LockPoint[] lockPoints = e.GetComponentsInChildren<LockPoint>();
		
		foreach (LockPoint l in lockPoints) {
			if (l.lockID == id) {
				return l;
			}
		}
		
		return null;
	}
	
	// Returns the Entity in the scene with the specified ID. If none exists, null is returned.
	private Entity findEntityByID(int id) {
		if (id != -1) {
			Entity[] entities = (Entity[]) Object.FindObjectsOfType(typeof(Entity));
			
			foreach (Entity e in entities) {
				if (e.entityID == id) {
					return e;
				}
			}
		}
		
		return null;
	}
	
	// Setters for the external references.
	public void setBuildScreenContainer(Transform buildScreenContainer) {
		this.buildScreenContainer = buildScreenContainer;
	}
	
	public void setWeaponContainer(Transform weaponContainer) {
		this.weaponContainer = weaponContainer;
	}
	
	public void setInventoryContainer(Transform inventoryContainer) {
		this.inventoryContainer = inventoryContainer;
	}
	
	public void setEmitter(ParticleSystem emitter) {
		this.emitter = emitter;
	}
	
	public void setMinimap(MiniMap map) {
		minimap = map;
	}
	private void updateOwner(Entity entity) {
		Transform owner = entity.transform;
		
		while (owner != null && owner.GetComponent<Damagable>() == null) {
			owner = owner.parent;
		}
		
		Debug.Log ("New owner: " + owner);
		
		entity.setOwner(owner);
	}
	

	
//	//SCORE
//	[RPC]
//	public void rpcJoined(NetworkViewID id){
//		ScoreBoardListener.inst().notify(ScoreBoardListener.Channel.ON_CONNECTED,new ScoreBoardEvent(id));
//	}
//	
//	//OLD
//	public void clearScore(){
//		playerGUI.clearPlayers();
//	}
//	
//	public void disconnect(NetworkViewID id){
//		networkView.RPC("disconnectRPC",RPCMode.All,id);
//	}
//	
//	public void syncScoreBoard(){
//		List<PlayerGUI.Score> scores = playerGUI.getScoreBoard();
//		foreach(PlayerGUI.Score s in scores){
//			networkView.RPC("scoreRPC",RPCMode.Others,s.ID,s.points);
//		}
//	}
//	[RPC]
//	void scoreRPC(NetworkViewID id,int score){
//		playerGUI.setPlayer(id,score,this);
//	}
//	[RPC]
//	void disconnectRPC(NetworkViewID id){
//		Debug.Log("view id has left :" + id);
//		if(!playerGUI.removePlayer(id)){
//			Debug.Log("Error Player not found");
//		}
//	}
}
	